This notebook reports on several approaches to selecting tree stems for individual-scale species classification, uniting Kueppers, Worsham et al. forest inventory data with Falco species classification map derived from the 2018 NEON spectroscopy mission. The approaches range from least to most conservative, that is, from preserving all trees to removing all but those in the uppermost canopy. Maps, agreement statistics, and agreement figures are shown for each approach.

Pipeline

  1. Align geolocated tree stem objects to classification raster.
  2. Define a buffer of radius \(r\) around each tree stem object, approximating crown area.
  3. Filter crown objects according to one of 4 specified procedures.
  4. Plot a map of crown objects and classification data at an example site.
  5. For each crown object, extract intersecting raster values by majority vote.
  6. Compute accuracy statistics.
  7. Generate agreement figures.

1. Least conservative approach: keep every tree

Map

Confusion matrix

Classification
ABLA PICO PIEN POTR UNKN
Reference ABLA 849 244 1615 2 0
PICO 10 535 23 0 0
PIEN 254 85 933 0 0
POTR 4 5 16 2 0
UNKN 1 0 0 0 0
Kappa Accuracy AccuracyLower AccuracyUpper
0.27 0.51 0.49 0.52

Probability density function

2. Less conservative

Keep any tree whose height is in the 90th percentile or higher. For all other trees, only keep them if (a) they don’t intersect another tree’s crown area or (b) if the tree whose area they intersect is not in the 90th percentile height.

  1. Apply a height-dependent buffer of radius \(r=0.03H + 0.5\), around every tree.
  2. For each tree \(t_i\):
# Create canopy filter
canopy.filter.a <- unlist(lapply(1:nrow(stem.buff), \(i) {
  if(stem.buff[i,]$Height < quantile(stem.buff$Height, .9)) {
    if(length(stem.within[[i]])<=1) { 
      tst <- stem.buff[i,]$Height > 0.9 * stem.buff[stem.overlap[[i]],]$Height &
        !any(stem.buff[stem.overlap[[i]],]$Height >= quantile(stem.buff$Height, .8))
      tst <- prod(tst)
    } else { 
      tst <- 0 }
  } else {
    tst <- 1
  }
  as.logical(tst)
}
))

# Apply canopy filter to buffered stems
stem.filt.a <- stem.buff[canopy.filter.a,]

Map

Confusion matrix

Classification
ABLA PICO PIEN POTR UNKN
Reference ABLA 158 45 282 0 0
PICO 2 117 7 0 0
PIEN 59 26 341 0 0
POTR 3 2 11 1 0
UNKN 0 0 0 0 0
Kappa Accuracy AccuracyLower AccuracyUpper
0.35 0.59 0.55 0.62

Probability density function

3. More conservative

Keep any tree whose height is in the 90th percentile or higher. For all other trees, only keep them if (a) they don’t intersect another tree’s crown area.

  1. Apply a height-dependent buffer of radius \(r=0.042H + 0.675\), around every tree.
  2. For each tree \(t_i\):
# Create canopy filter
canopy.filter.b <- unlist(lapply(1:nrow(stem.buff), \(i) {
  if(stem.buff[i,]$Height < quantile(stem.buff$Height, .90)) {
    if(length(stem.within[[i]])<=1) { 
      if(length(stem.overlap[[i]])) {
      tst <- 0
      } else {
        tst <- 1
      }
    } else {
      tst <- 0
    }
  } else {
  tst <- 1
  }
  as.logical(tst)
  }
)) 

# Apply canopy filter
stem.filt.b <- stem.buff[canopy.filter.b,]

Map

Confusion matrix

Classification
ABLA PICO PIEN POTR UNKN
Reference ABLA 97 11 191 0 0
PICO 0 12 3 0 0
PIEN 45 15 282 0 0
POTR 0 1 1 0 0
UNKN 0 0 0 0 0
Kappa Accuracy AccuracyLower AccuracyUpper
0.23 0.59 0.56 0.63
Class Sensitivity Specificity Pos.Pred.Value Neg.Pred.Value Precision Recall F1 Prevalence Detection.Rate Detection.Prevalence Balanced.Accuracy
Class: ABLA 0.68 0.61 0.32 0.87 0.32 0.68 0.44 0.22 0.15 0.45 0.65
Class: PICO 0.31 1.00 0.80 0.96 0.80 0.31 0.44 0.06 0.02 0.02 0.65
Class: PIEN 0.59 0.67 0.82 0.38 0.82 0.59 0.69 0.72 0.43 0.52 0.63
Class: POTR NA 1.00 NA NA 0.00 NA NA 0.00 0.00 0.00 NA
Class: UNKN NA 1.00 NA NA NA NA NA 0.00 0.00 0.00 NA

Probability density function

Most conservative

Keep only trees in the 90th percentile of height or higher.

Map

Confusion matrix

Classification
ABLA PICO PIEN POTR UNKN
Reference ABLA 65 4 122 0 0
PICO 0 2 3 0 0
PIEN 39 10 246 0 0
POTR 0 0 0 0 0
UNKN 0 0 0 0 0
Kappa Accuracy AccuracyLower AccuracyUpper
0.22 0.64 0.59 0.68
Class Sensitivity Specificity Pos.Pred.Value Neg.Pred.Value Precision Recall F1 Prevalence Detection.Rate Detection.Prevalence Balanced.Accuracy
Class: ABLA 0.62 0.67 0.34 0.87 0.34 0.62 0.44 0.21 0.13 0.39 0.65
Class: PICO 0.12 0.99 0.40 0.97 0.40 0.12 0.19 0.03 0.00 0.01 0.56
Class: PIEN 0.66 0.59 0.83 0.36 0.83 0.66 0.74 0.76 0.50 0.60 0.63
Class: POTR NA 1.00 NA NA NA NA NA 0.00 0.00 0.00 NA
Class: UNKN NA 1.00 NA NA NA NA NA 0.00 0.00 0.00 NA

Probability density function

Most conservative, with a static 3 px window

Keep only trees in the 90th percentile of height or higher and search only 3 px around

Map

Confusion matrix

## ABLA PICO PIEN POTR UNKN NA's 
##  116   22  329    0    0   33
Classification
ABLA PICO PIEN POTR UNKN
Reference ABLA 67 10 101 0 0
PICO 0 2 3 0 0
PIEN 49 10 225 0 0
POTR 0 0 0 0 0
UNKN 0 0 0 0 0
Kappa Accuracy AccuracyLower AccuracyUpper
0.22 0.63 0.58 0.67

Probability density function

Compare accuracy

Run Kappa Accuracy AccuracyLower AccuracyUpper
Least Conserv 0.27 0.51 0.49 0.52
Less Conserv 0.35 0.59 0.55 0.62
More Conserv 0.23 0.59 0.56 0.63
Most Conserv - Moving window 0.22 0.64 0.59 0.68
Most Conserv - 3px window 0.22 0.63 0.58 0.67

Sanity check: maps of best-performing approach at every site

## [[1]]

## 
## [[2]]

## 
## [[3]]

## 
## [[4]]

## 
## [[5]]

## 
## [[6]]

## 
## [[7]]

## 
## [[8]]

## 
## [[9]]

## 
## [[10]]

## 
## [[11]]

## 
## [[12]]

## 
## [[13]]

## 
## [[14]]

## 
## [[15]]

## 
## [[16]]

## 
## [[17]]